/**
 * Copyright 2008 Matthew DeLambo
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.delambo.drool.pooling;

import com.delambo.drool.interfaces.*;
import java.sql.*;
import java.util.*;

/**
 * Spins a new timer thread at a fixed rate that manages and cleans nodes and
 * connections in a pool.
 * @author delambo
 */
public final class CleanerThread_Impl <NODE extends PoolNode, R>
        extends TimerTask 
        implements CleanerThread<NODE> {
    final private String cleanupStyle;
    final private AtomicLockingArray<NODE> pool;
    final private DataSource ds;
    final private int leaseTime, expirationTime, timerInterval;
    final private boolean autoCommit;
    final private Logger logger;
    final private PoolNode.Factory pnFactory;
    final private Resource.Factory resourceFactory;
    final private Timer timer;
    
    public CleanerThread_Impl(DataSource ds, AtomicLockingArray pool, 
            Logger logger, PoolNode.Factory pnFactory, Timer timer,
            Resource.Factory resourceFactory, String cleanupStyle, int leaseTime, 
            int expireTime, boolean commit, int interval) {
        this.cleanupStyle = cleanupStyle;
        this.ds = ds;
        this.pool = pool;
        this.logger = logger;
        this.pnFactory = pnFactory;
        this.resourceFactory = resourceFactory;
        this.timer = timer;
        this.leaseTime = leaseTime;
        this.autoCommit = commit;
        this.expirationTime = expireTime;
        this.timerInterval = interval;
    }
    
    /**
     * Starts this thread.
     */
    @Override public void run() {
        cleanNodes();
    }
    
    /**
     * Stops this cleaning thread and timer.
     * @return true if thread was cancelled without exception; false, otherwise.
     */
    @Override public boolean cancel() {
        try {
            this.cancel();
            timer.cancel();
            return true;
        } catch(Exception e) { logger.all("Fail in cleaner cancel...", e); }
        return false;
    }
    
    /**
     * Starts the timer thread.
     * @param interval fixed rate for timer.
     */
    public void startTimer() {
        timer.scheduleAtFixedRate(this, timerInterval, timerInterval);
    }
    
    /**
     * Cleans the pool nodes depending on the cleanup style, rotate or refresh.
     * Pool nodes are not thread-safe because node resources are also modified 
     * and shared by the Pool_Impl.getResource(), therefore locking is used. 
     */
    private void cleanNodes() {
        for(int i = 0; i < pool.getSize(); i++) {
            boolean gotlock = false;
            try {
                if(gotlock = pool.tryLock(i)) {
                    NODE node = pool.getNode(i);
                    if(node == null || needsCleaned(node)) {
                        if(cleanupStyle.equalsIgnoreCase("refresh") 
                                && node.resourceValidates())
                            node.cleanUp();
                        else
                            replaceNode(i, node);
                    }
                }
            } finally { 
                if(gotlock) 
                    pool.unlock(i); 
            }
        }
    }
    
    /**
     * Tests if a pool node needs to be cleaned.
     * @param node node to check against leasetime and expiration time.
     * @return true if node is null, old, or past its lease; otherwise, false.
     */
    private boolean needsCleaned(NODE node) {
        return (node.getResource() == null
                || ((node.getLastUse() > 0) && (node.getLastUse()
                        < (System.currentTimeMillis() - expirationTime)))
                || ((node.getLeaseTime() > 0) && (node.getLeaseTime() 
                            < (System.currentTimeMillis() - leaseTime))));
    } 
    
    /**
     * Removes a node in a pool and instantiates a new node in its place.
     * @param allocation index in pool to be replaced
     * @param node node to replace.
     */
    private void replaceNode(int allocation, NODE n) {
        killNode(n);
        NODE node = (NODE) pnFactory.create(
                resourceFactory.create(ds.get(), autoCommit));
        node.init();
        pool.setNode(allocation, node);
    } 
    
    /**
     * Closes the connection and nulls-out the node node.
     * @param node node from pool we want destroyed.
     */
    private void killNode(NODE n) {
        if(n != null) {
            n.killResource();
            n = null;
        }
    }
}
